from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
import bcrypt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
import redis  # 启用Redis

from database import get_db, User
from config import settings

# JWT配置
SECRET_KEY = settings.secret_key
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = settings.access_token_expire_minutes

# Redis连接
redis_client = redis.from_url(settings.redis_url)  # 启用Redis

# 认证方案
security = HTTPBearer()


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证密码"""
    try:
        # 对于bcrypt哈希（以$2b$或$2a$开头），直接使用bcrypt验证
        if hashed_password.startswith(('$2b$', '$2a$')):
            return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
        # 对于其他格式的哈希，尝试使用bcrypt验证（向后兼容）
        # 如果失败，则返回False
        try:
            return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
        except ValueError:
            # ValueError表示盐值无效，即哈希格式不正确
            return False
    except Exception:
        # 如果验证失败，返回False
        return False


def get_password_hash(password: str) -> str:
    """生成密码哈希"""
    # 处理密码长度限制：bcrypt最多支持72字节
    # 如果密码超过72字节，使用SHA256进行预处理
    if len(password.encode('utf-8')) > 72:
        import hashlib
        password = hashlib.sha256(password.encode('utf-8')).hexdigest()
    # 确保密码不超过72字节
    password = password[:72]
    # 使用bcrypt生成哈希
    hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
    return hashed.decode('utf-8')


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """创建访问令牌"""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


def authenticate_user(db: Session, username: str, password: str) -> Optional[User]:
    """验证用户"""
    user = db.query(User).filter(User.username == username).first()
    if not user:
        return None
    if not verify_password(password, user.hashed_password):
        return None
    return user


def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security),
    db: Session = Depends(get_db)
) -> User:
    """获取当前用户"""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )
    
    try:
        payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    
    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise credentials_exception
    
    # 启用Redis会话检查
    session_key = f"user_session:{user.id}"
    if not redis_client.exists(session_key):
        raise credentials_exception
    
    return user


def create_user_session(user: User) -> str:
    """创建用户会话"""
    # 启用Redis会话管理
    session_key = f"user_session:{user.id}"
    session_data = {
        "user_id": user.id,
        "username": user.username,
        "login_time": datetime.utcnow().isoformat()
    }
    
    # 存储会话到Redis，设置过期时间
    redis_client.hmset(session_key, session_data)
    redis_client.expire(session_key, ACCESS_TOKEN_EXPIRE_MINUTES * 60)
    
    return session_key


def get_current_user_from_token(token: str, db: Session) -> User:
    """从令牌获取当前用户（用于查询参数认证）"""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )
    
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    
    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise credentials_exception
    
    # 启用Redis会话检查
    session_key = f"user_session:{user.id}"
    if not redis_client.exists(session_key):
        raise credentials_exception
    
    return user


def delete_user_session(user_id: str):
    """删除用户会话"""
    # 启用Redis会话管理
    session_key = f"user_session:{user_id}"
    redis_client.delete(session_key)